package com.dcy.gateway.satoken.filter;

import cn.dev33.satoken.filter.SaFilterAuthStrategy;
import cn.dev33.satoken.filter.SaFilterErrorStrategy;
import cn.dev33.satoken.reactor.context.SaReactorHolder;
import cn.dev33.satoken.reactor.context.SaReactorSyncHolder;
import cn.dev33.satoken.router.SaRouterUtil;
import cn.dev33.satoken.session.SaSession;
import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.util.SaTokenConsts;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.URLUtil;
import com.alibaba.fastjson.JSON;
import com.dcy.common.constant.Constant;
import com.dcy.common.model.LoginAccount;
import com.dcy.common.model.R;
import com.dcy.system.api.api.AResourcesService;
import com.dcy.system.api.model.Resources;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.config.annotation.DubboReference;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.util.Collections;
import java.util.List;

/**
 * @Author：dcy
 * @Description: sa-token全局过滤器
 * @Date: 2021/4/13 16:33
 */
@Slf4j
@RefreshScope
@Component
public class SaTokenFilter implements GlobalFilter, Ordered {

    private final AntPathMatcher antPathMatcher = new AntPathMatcher();

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    @DubboReference(version = "1.0.0", check = false)
    private AResourcesService aResourcesService;

    /**
     * 拦截路由
     */
    @Value("${spring.sa-token.include-list}")
    private List<String> includeList;

    /**
     * 放行路由
     */
    @Value("${spring.sa-token.exclude-list}")
    private List<String> excludeList;


    /**
     * 是否需要过滤
     */
    private boolean shouldFilter(ServerWebExchange exchange) {
        // 不进行拦截的地址
        String url = getUrl(exchange);
        return excludeList.stream().noneMatch(pattern -> antPathMatcher.match(pattern, url));
    }

    // ------------------------ 执行函数

    /**
     * 认证函数：每次请求执行
     */
    public SaFilterAuthStrategy auth = r -> {
        // 权限拦截
        if (r != null) {
            LoginAccount loginOutputDTO = (LoginAccount) r;
            if (Constant.ONE.equalsIgnoreCase(loginOutputDTO.getUserType())) {
                authFilter();
            }
        } else {
            authFilter();
        }

    };


    /**
     * 异常处理函数：每次[认证函数]发生异常时执行此函数
     */
    public SaFilterErrorStrategy error = e -> {
        log.warn("sa-token 警告 {}",e.getMessage());
        return R.error(3000, e.getMessage());
    };


    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        // ---------- 全局认证处理
        if (shouldFilter(exchange)) {
            return doFilter(exchange, chain);
        }
        return chain.filter(exchange);
    }

    private Mono<Void> doFilter(ServerWebExchange exchange, GatewayFilterChain chain) {
        // 用户信息对象
        LoginAccount loginOutputDTO;
        try {
            // 写入全局上下文 (同步)
            SaReactorSyncHolder.setContent(exchange);
            // 获取用户信息
            loginOutputDTO = getLoginOutputDTO();
            // 执行全局过滤器
            SaRouterUtil.match(includeList, excludeList, () -> auth.run(loginOutputDTO));

        } catch (Throwable e) {
            // 1. 获取异常处理策略结果
            Object result = error.run(e);

            // 2. 写入输出流
            if (exchange.getResponse().getHeaders().getFirst(HttpHeaders.CONTENT_TYPE) == null) {
                exchange.getResponse().getHeaders().set(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_UTF8_VALUE);
            }
            return exchange.getResponse().writeWith(Mono.just(exchange.getResponse().bufferFactory().wrap(JSON.toJSONBytes(result))));

        } finally {
            // 清除上下文
            SaReactorSyncHolder.clearContent();
        }

        // ---------- 执行

        // 写入全局上下文 (同步)
        SaReactorSyncHolder.setContent(exchange);
        if (loginOutputDTO != null) {
            ServerHttpRequest newRequest = exchange.getRequest().mutate().headers(httpHeaders -> {
                httpHeaders.add(Constant.USER_ID, loginOutputDTO.getId());
                httpHeaders.add(Constant.USER_NAME, loginOutputDTO.getUsername());
            }).build();
            exchange = exchange.mutate().request(newRequest).build();
        }
        // 执行
        ServerWebExchange finalExchange = exchange;
        return chain.filter(finalExchange).subscriberContext(ctx -> {
            // 写入全局上下文 (异步)
            ctx = ctx.put(SaReactorHolder.CONTEXT_KEY, finalExchange);
            return ctx;
        }).doFinally(r -> {
            // 清除上下文
            SaReactorSyncHolder.clearContent();
        });
    }

    /**
     * 获取用户信息
     *
     * @return
     */
    private LoginAccount getLoginOutputDTO() {
        // 获取用户信息
        SaSession session = StpUtil.getSession(false);
        if (session != null) {
            return session.getModel(Constant.SESSION_USER_KEY, LoginAccount.class);
        }
        return null;
    }

    /**
     * 获取请求中的url
     */
    private String getUrl(ServerWebExchange exchange) {
        ServerHttpRequest request = exchange.getRequest();
        String requestUri = request.getPath().pathWithinApplication().value();
        return URLUtil.getPath(requestUri);
    }

    /**
     * 获取路由信息
     *
     * @return
     */
    private List<Resources> getResources() {
        List<Resources> resourcesList = (List<Resources>) redisTemplate.opsForValue().get(Constant.REDIS_ROUTER_LIST);
        if (CollUtil.isEmpty(resourcesList)) {
            resourcesList = aResourcesService.getRouterList();
        }
        return resourcesList;
    }

    /**
     * 权限过滤
     */
    private void authFilter() {
        ServerWebExchange serverWebExchange = SaReactorSyncHolder.getContent();
        String url = URLUtil.getPath(serverWebExchange.getRequest().getURI().getPath());
        // 获取所有的路由表
        List<Resources> resourcesList = getResources();
        for (Resources resources : resourcesList) {
            // 如果URL匹配成功就不循环了，直接退出循环
            // 如果匹配不成功就匹配 /** URL路径了
            if (antPathMatcher.match(resources.getResPath(), url) && serverWebExchange.getRequest().getMethod().name().equalsIgnoreCase(resources.getHttpMethod())) {
                SaRouterUtil.match(Collections.singletonList(resources.getResPath()), () -> {
                    StpUtil.checkPermission(resources.getResCode());
                });
                break;
            }
        }
    }

    @Override
    public int getOrder() {
        return SaTokenConsts.ASSEMBLY_ORDER;
    }
}
